﻿//-----------------------------------------------------------------------
// <copyright file="Plural.cs" company="Rhys Parry">
//    Copyright Rhys Parry 2008
//    This file is released under the Apache 2.0 License
// </copyright>
//-----------------------------------------------------------------------
namespace Smarts.Core
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// This class is used to produce english pluralisations of words
    /// </summary>
    public static class Plural
    {
        /// <summary>
        /// Holds the exceptions to pluralisation rules
        /// </summary>
        private static Dictionary<string, string> exceptions = new Dictionary<string, string>();

        /// <summary>
        /// Defines whether negatives are expected and to be allowed
        /// </summary>
        private static bool allowNegatives = true;

        /// <summary>
        /// Gets or sets a value indicating whether a negative value is acceptable for the
        /// <see cref="Of(int, string)">PluralForm</see> method
        /// </summary>
        public static bool AllowNegatives
        {
            get
            {
                return allowNegatives;
            }

            set
            {
                allowNegatives = value;
            }
        }

        /// <summary>
        /// Adds an exception to the normal pluralization rules.
        /// </summary>
        /// <param name="singularForm">The singular form of the word</param>
        /// <param name="pluralForm">The plural form</param>
        public static void AddException(string singularForm, string pluralForm)
        {
            if (exceptions.ContainsKey(singularForm))
            {
                exceptions[singularForm] = pluralForm;
            }
            else
            {
                exceptions.Add(singularForm, pluralForm);
            }
        }

        /// <summary>
        /// Removes an expection and reverts to normal pluralization rules.
        /// </summary>
        /// <param name="singularForm">The singular form of the word to remove</param>
        public static void RemoveException(string singularForm)
        {
            if (exceptions.ContainsKey(singularForm))
            {
                exceptions.Remove(singularForm);
            }
        }

        /// <summary>
        /// Resets all exceptions so that normal rules are used.
        /// </summary>
        public static void ClearAllExceptions()
        {
            exceptions = new Dictionary<string, string>();
        }

        /// <summary>
        /// Uses a defined plural form, adding it to the exception list
        /// (for future reference) and chooses to use the singular or
        /// plural version based on the number specified
        /// </summary>
        /// <param name="number">The number of instances of Singular</param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <param name="pluralForm">The plural form of the word</param>
        /// <returns>The number, followed by the plural form (or singular form if Number == 1)</returns>
        public static string Of(int number, string singularForm, string pluralForm)
        {
            AddException(singularForm, pluralForm);
            return Of(number, singularForm);
        }

        /// <summary>
        /// Calculates the plural form of a word, using exceptions if one
        /// is specified
        /// </summary>
        /// <param name="number">The number of instances of Singular</param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <returns>The number, followed by the plural form (or singular form if Number == 1)</returns>
        public static string Of(int number, string singularForm)
        {
            NegativeCheck(number);
            if (Math.Abs(number) == 1)
            {
                return string.Format("{0} {1}", number, singularForm);
            }

            if (exceptions.ContainsKey(singularForm))
            {
                return string.Format("{0} {1}", number, exceptions[singularForm]);
            }
            
            if (singularForm.EndsWith("s") ||
                singularForm.EndsWith("ch") ||
                singularForm.EndsWith("sh") ||
                singularForm.EndsWith("o"))
            {
                return string.Format("{0} {1}es", number, singularForm);
            }

            return singularForm.EndsWith("y") && !(
                                                      singularForm.EndsWith("ay") ||
                                                      singularForm.EndsWith("ey") ||
                                                      singularForm.EndsWith("oy") ||
                                                      singularForm.EndsWith("uy"))
                       ? string.Format("{0} {1}ies", number, singularForm.Substring(0, singularForm.Length - 1))
                       : string.Format("{0} {1}s", number, singularForm);
        }

        /// <summary>
        /// Used to calculate whether is or are should be used
        /// in a plural phrase. Also adds an exception for
        /// <paramref name="singularForm"/> to <paramref name="pluralForm"/>
        /// </summary>
        /// <param name="number">The number of instances of <paramref name="singularForm"/></param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <param name="pluralForm">The plural form of the word</param>
        /// <returns>is/are (as appropriate) followed by <see cref="Of(int, string)"/></returns>
        public static string Is(int number, string singularForm, string pluralForm)
        {
            AddException(singularForm, pluralForm);
            return Is(number, singularForm);
        }

        /// <summary>
        /// Used to calculate whether is or are should be used
        /// in a plural phrase.
        /// </summary>
        /// <param name="number">The number of instances of <paramref name="singularForm"/></param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <returns>is/are (appropriate) followed by <see cref="Of(int, string)"/></returns>
        public static string Is(int number, string singularForm)
        {
            NegativeCheck(number);
            return number == 1
                       ? string.Format("is {0}", Of(number, singularForm))
                       : string.Format("are {0}", Of(number, singularForm));
        }

        /// <summary>
        /// Used to calculate whether was or were should be used
        /// in a plural phrase. Also adds an exception for
        /// <paramref name="singularForm"/> to <paramref name="pluralForm"/>
        /// </summary>
        /// <param name="number">The number of instances of <paramref name="singularForm"/></param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <param name="pluralForm">The plural form of the word</param>
        /// <returns>was/were (as appropriate) followed by <see cref="Of(int, string)"/></returns>
        public static string Was(int number, string singularForm, string pluralForm)
        {
            AddException(singularForm, pluralForm);
            return Was(number, singularForm);
        }

        /// <summary>
        /// Used to calculate whether was or were should be used
        /// in a plural phrase.
        /// </summary>
        /// <param name="number">The number of instances of <paramref name="singularForm"/></param>
        /// <param name="singularForm">The singular form of the word</param>
        /// <returns>was/were (appropriate) followed by <see cref="Of(int, string)"/></returns>
        public static string Was(int number, string singularForm)
        {
            NegativeCheck(number);
            if (number == 1)
            {
                return string.Format("was {0}", Of(number, singularForm));
            }

            return "were " + Of(number, singularForm);
        }

        /// <summary>
        /// Checks whether <paramref name="number"/> is negative
        /// and if negatives are not allowed throws an
        /// <see cref="ArgumentOutOfRangeException"/>
        /// </summary>
        /// <param name="number">The number to check</param>
        private static void NegativeCheck(int number)
        {
            if (!AllowNegatives && number < 0)
            {
                throw new ArgumentOutOfRangeException("number", "Negative numbers have been disabled");
            }
        }
    }
}
